/*
 * Copyright 2007 Agile Plugins Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * 	http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.googlecode.agileplugins.monkey;

import com.intellij.ide.fileTemplates.FileTemplateManager;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.actionSystem.DataKeys;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.command.CommandProcessor;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.psi.*;
import com.intellij.psi.search.GlobalSearchScope;

import javax.swing.*;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;

/**
 * Created by IntelliJ IDEA.
 * User: rob
 * Date: Oct 4, 2006
 * Time: 5:07:54 PM
 * To change this template use File | Settings | File Templates.
 */
public class CreateFactoryAction extends AnAction {

    public void actionPerformed(AnActionEvent anActionEvent) {
        DataContext datacontext = anActionEvent.getDataContext();
        final Project project = DataKeys.PROJECT.getData(datacontext);
        if (project == null) {
            System.out.println("no project");
            return;
        }
        PsiDocumentManager.getInstance(project).commitAllDocuments();
        PsiElement element = DataKeys.PSI_ELEMENT.getData(datacontext);
        PsiFile psiFile = DataKeys.PSI_FILE.getData(datacontext);

        System.out.println("element = " + element);
        System.out.println("psiFile = " + psiFile);
        if (element instanceof PsiClass && psiFile != null) {
            process(project, (PsiClass) element, psiFile);
        }
    }

    protected PsiElement getElement(Editor editor, PsiFile psiFile) {
        int offset = editor.getCaretModel().getOffset();
        PsiElement element = psiFile.findElementAt(offset);
        return element;
    }

    private void process(Project project, PsiClass psiClass, PsiFile psiFile) {
        String interfaceMessage = "You have chosen to create a Factory for an interface.";
        if (psiClass.isInterface()) {
            psiClass = findImplementation(psiClass);
            if (psiClass != null) {
                int result = Messages.showYesNoDialog(interfaceMessage + "\nWould you like to construct instances of the default implementation\n" + psiClass.getQualifiedName() + "?", "Create Factory", Messages.getWarningIcon());
                if (result != 0) {
                    return;
                }
            } else {
                Messages.showErrorDialog(interfaceMessage + "\nNo default implementations could be found.", "Create Factory");
                return;
            }
        }

        PsiFile psiJavaFile = psiClass.getContainingFile();
        PsiDirectory psidirectory = psiJavaFile.getContainingDirectory();
        if (psidirectory == null || !psidirectory.isWritable()) {
            System.out.println("psidirectory null or not writable = " + psidirectory);
            ;
            psidirectory = psiFile.getContainingDirectory();
        }

        if (psidirectory == null || !psidirectory.isWritable()) {
            System.out.println("local psidirectory null or not writable = " + psidirectory);
            ;
            return;
        }
        createFactory(project, psiClass, psidirectory);
    }

    private PsiClass findImplementation(PsiClass psiClass) {
        String qualifiedName = psiClass.getQualifiedName();
        int dot = qualifiedName.lastIndexOf('.');
        System.out.println("qualifiedName = " + qualifiedName);
        String defaultName = qualifiedName.substring(0, dot + 1) + "Default" + qualifiedName.substring(dot + 1);
        System.out.println("defaultName = " + defaultName);
        GlobalSearchScope globalsearchscope = GlobalSearchScope.allScope(psiClass.getProject());
        return psiClass.getManager().findClass(defaultName, globalsearchscope);

    }

    private void createFactory(Project project, PsiClass psiClass, PsiDirectory destinationDirectory) {
        System.out.println("createFactory(" + psiClass + ")");
        PsiMethod[] constructors = psiClass.getConstructors();
        List publicConstructors = new ArrayList();
        for (int i = 0; i < constructors.length; i++) {
            PsiMethod constructor = constructors[i];
            if (constructor.getModifierList().hasModifierProperty("public")) {
                publicConstructors.add(constructor);
            }
        }
        if (publicConstructors.size() == 0) {
            Messages.showErrorDialog("This class has no public constructors", "No Constructors");
            return;
        }
        PsiMethod constructor = null;
        if (publicConstructors.size() == 1) {
            constructor = (PsiMethod) publicConstructors.get(0);
        } else {
            String[] constructorOptions = createOptions(publicConstructors);
            int option = Messages.showChooseDialog("This class has multiple constructors.\nPlease choose a constructor to add to the factory", "Create Factory", constructorOptions, constructorOptions[0], Messages.getQuestionIcon());
            if (option == -1) {
                return;
            }
            constructor = (PsiMethod) publicConstructors.get(option);
        }
        PsiParameterList parameterList = constructor.getParameterList();
        PsiParameter[] parameters = parameterList.getParameters();
        String[] paramOptions = new String[parameters.length];
        for (int i = 0; i < parameters.length; i++) {
            PsiParameter parameter = parameters[i];
            paramOptions[i] = parameter.getText();
        }
        List targetFieldsList = new ArrayList();
        List factoryFieldsList = new ArrayList();
        List methodParamsList = new ArrayList();
        Properties properties = getProperties(psiClass, destinationDirectory);
        if (parameters.length > 0) {
            MessageDialog messageDialog = new MessageDialog("Create Factory", "Please choose the static parameters for this class\tto add to the factory constructor.\nUnselected fields will be passed into the create method.", null, "OptionPane.questionIcon", paramOptions, true, null, ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
            messageDialog.show();
            if (!messageDialog.isOK()) {
                return;
            }
            int[] selectedListIndexes = messageDialog.getSelectedListIndexes();
            for (int i = 0; i < parameters.length; i++) {
                ParameterInfo pi = new ParameterInfo(parameters[i].getType(), parameters[i].getName());
                targetFieldsList.add(pi);
            }

            boolean[] staticFields = new boolean[parameters.length];
            for (int i = 0; i < selectedListIndexes.length; i++) {
                int selectedListIndex = selectedListIndexes[i];
                staticFields[selectedListIndex] = true;
            }
            for (int i = 0; i < parameters.length; i++) {
                if (staticFields[i]) {
                    factoryFieldsList.add(targetFieldsList.get(i));
                } else {
                    methodParamsList.add(targetFieldsList.get(i));
                }
            }
            PsiManager manager = psiClass.getManager();
            PropertiesHelper.addFieldProperties("TARGET_FIELD", properties, targetFieldsList, manager, project);
            PropertiesHelper.addFieldProperties("FIELD", properties, factoryFieldsList, manager, project);
            PropertiesHelper.addFieldProperties("PARAM", properties, methodParamsList, manager, project);
        }

        final Runnable runnable = new CreateComponentHandler(project, destinationDirectory, properties);

        CommandProcessor.getInstance().executeCommand(project,
                new Runnable() {
                    public void run() {
                        ApplicationManager.getApplication().runWriteAction(runnable);
                    }
                }, "Create Component", null);
    }


    private Properties getProperties(PsiClass psiClass, PsiDirectory psiDirectory) {
        Properties properties = new Properties(FileTemplateManager.getInstance().getDefaultProperties());
        properties.setProperty("TEMPLATE_TYPE", "Factory");
        String packageName = psiDirectory.getPackage().getQualifiedName();
        properties.setProperty("PACKAGE_NAME", packageName);
        String targetClassName = psiClass.getName();
        System.out.println("targetClassName = " + targetClassName);
        String targetInterfaceName = targetClassName;
        if (targetClassName.startsWith("Default")) {
            targetInterfaceName = targetClassName.substring(7);
        }
        String factoryName = targetInterfaceName + "Factory";
        properties.setProperty("CLASS_NAME", "Default" + factoryName);
        System.out.println("factoryName = " + factoryName);
        properties.setProperty("INTERFACE_NAME", factoryName);
        properties.setProperty("INSTANCE_NAME", StringUtil.lcFirst(factoryName));
        properties.setProperty("TARGET_CLASS_NAME", targetClassName);
        properties.setProperty("TARGET_INTERFACE_NAME", targetInterfaceName);
        properties.setProperty("TARGET_INSTANCE_NAME", StringUtil.lcFirst(targetInterfaceName));


        return properties;
    }


    private String[] createOptions(List publicConstructors) {
        String[] options = new String[publicConstructors.size()];
        int cnt = 0;
        for (Iterator iterator = publicConstructors.iterator(); iterator.hasNext();) {
            PsiMethod psiMethod = (PsiMethod) iterator.next();
            options[cnt++] = psiMethod.getName() + psiMethod.getParameterList().getText();
        }
        return options;
    }

}